package org.chiefmedicalofficer.hello;

/*
 * 

   Copyright 2009 ChiefMedicalOfficer.org
   
   Licensed under the Apache License, Version 2.0 (the "License"); 
   you may not use this file except in compliance with the License. 
   You may obtain a copy of the License at 
   
   http://www.apache.org/licenses/LICENSE-2.0 
   
   Unless required by applicable law or agreed to in writing, software distributed
   under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
   CONDITIONS OF ANY KIND, either express or implied. See the License for the 
   specific language governing permissions and limitations under the License.
   


Although not required, it is encouraged to submit modifications or improvements to
this code back to the community.

http://code.google.com/p/chiefmedicalofficer/source/browse/#svn/trunk/src/java/HelloGoogleHealth

  Author:   	Tom Wilson
				tom@nerdpod.org


  Date: 	2009-11-11
  Version: 	0.1

 */

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.gdata.client.Query;
import com.google.gdata.client.health.H9Service;
import com.google.gdata.data.Category;
import com.google.gdata.data.Feed;
import com.google.gdata.data.health.ContinuityOfCareRecord;
import com.google.gdata.data.health.ProfileEntry;
import com.google.gdata.data.health.ProfileFeed;
import com.google.gdata.data.Entry;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

/*
 * Demonstrates a simple login and two data extraction methods:
 * 
 * 1) Pull each profile into its own map of clinical events
 * 
 * 2) Pull all clinical events and map by categories.
 * Within each category, a multimap of each family
 * member's clinical events.
 * 
 * Clinical events are stored as XML fragments using 
 * the CCRg schema. Each fragment represents one event.
 */
public class GoogleHealth {

	/*
	 * Maps each profile name to the REST ID necessary for 
	 * data read/write.
	 */
	private Map<String, String> profileLookup;
	/*
	 * Optional fast index of profile names for sorting.
	 */
	private List<String> profileIndex;

	/*
	 * Stores a simple map of family medical data.
	 * 
	 * Maps a profile name to the complete CCRg record.
	 */
	private Map<String, String> mapByProfile;
	/*
	 * Alternate data structure using Google Multimaps.
	 * 
	 * First maps each clinical event gategory to a 
	 * google multimap mapping profile name to 
	 * multiple CCRg fragments.
	 */
	private Map<String, Multimap<String, String>> mapByCategory;

	/*
	 * GHealth utility stuff
	 */
	private H9Service h9Service;
	private static final String PROFILE_LIST_URL =
		"https://www.google.com/h9/feeds/profile/list";
	private static final String PROFILE_URL =
		"https://www.google.com/h9/feeds/profile/ui/";

	/*
	 * Public constructor instantiates the service object.
	 */
	public GoogleHealth() {
		h9Service = new H9Service("hello-google-health");
	}

	/*
	 * The simplest possible login. 
	 * After authentication it creates the family profile lookup map.
	 * 
	 * Setup of secure OAuth framework is left to the user as an exercise.
	 */
	public Boolean login(String username, String password) {
		Boolean authenticated = false;
		try {
			h9Service.setUserCredentials(username, password);
			authenticated = true;
		} catch (AuthenticationException e) {
			System.out.println("Could not log in!");
		}
		if (authenticated) {
			// Populate the index of profiles
			try {
				profileLookup = createProfileLookupMap();
			} catch (Exception ex) {
				authenticated = false;
			}
		}
		return authenticated;
	}

	/*
	 * First step for any family medical profile
	 * is to create a lookup map of family members.
	 */
	private Map<String, String> createProfileLookupMap()
			throws MalformedURLException, IOException, ServiceException {
		Map<String, String> profileMap = new HashMap<String, String>();

		Feed profileListFeed = h9Service.getFeed(new URL(PROFILE_LIST_URL),
				Feed.class);
		List<Entry> entries = profileListFeed.getEntries();

		// Iterate through profile entries, add name and URI to map
		for (Entry profileListEntry : entries) {
			String name = profileListEntry.getTitle().getPlainText();
			String uri = profileListEntry.getPlainTextContent();
			profileMap.put(name, uri);
		}
		return profileMap;
	}

	/*
	 * Populates the class property mapByProfile, a simple map
	 * between profile name and CCRg record
	 */
	public void populateProfileToCCRMap() throws IOException, ServiceException {
		mapByProfile = new HashMap<String, String>();

		// We used the digest=true parameter, so there should only
		// be a single Atom entry that contains all of the CCR data in
		// profile.
		for (String profileName : profileLookup.keySet()) {
			String profileId = profileLookup.get(profileName);
			final URL profileURL = new URL(PROFILE_URL + profileId);
			final Query query = new Query(profileURL);
			// Set digest=true to return the CCR document in 
			// one big chunk per profile
			query.addCustomParameter(
					new Query.CustomParameter("digest",	"true"));
			final ProfileFeed profileFeed =
				h9Service.getFeed(query, ProfileFeed.class);
			// only one entry containing entire CCR
			ProfileEntry entry = profileFeed.getEntries().get(0);
			ContinuityOfCareRecord ccr = entry.getContinuityOfCareRecord();
			if (ccr == null) {
				System.out.println("CCR is null!");
				continue;
			}
			String ccrData = entry.getContinuityOfCareRecord().getXmlBlob()
			.getBlob();
			mapByProfile.put(profileName, ccrData);
		}
	}

	/*
	 * Populates the map-inversion family medical record.
	 * The first level is a simple map with clinical event category 
	 * as the key. 
	 * 
	 * The value of the first map is a google multimap. 
	 * 
	 * The multimap uses the profile name as the key and the 
	 * value is a CCRg fragment for that category and profile.
	 * 
	 * This allows for display of an entire family within 
	 * a single clinical event category.
	 */
	public void populateCategoryToProfileMap() throws IOException,
			ServiceException {
		mapByCategory = new HashMap<String, Multimap<String, String>>();
		for (String profileName : profileLookup.keySet()) {
			final URL profileURL = new URL(PROFILE_URL
					+ profileLookup.get(profileName));
			final Query query = new Query(profileURL);
			final ProfileFeed profileFeed = h9Service.getFeed(query,
					ProfileFeed.class);
			int i = 0;
			for (ProfileEntry entry : profileFeed.getEntries()) {
				String categoryName = getCategory(entry);
				ContinuityOfCareRecord ccr =
					entry.getContinuityOfCareRecord();
				if (ccr == null) {
					System.out
							.println("Found a NULL CCR entry. That's not right!");
					continue;
				}
				String ccrData = entry.getContinuityOfCareRecord().getXmlBlob()
						.getBlob();
				if (!mapByCategory.containsKey(categoryName)) {
					// adding a new category
					Multimap<String, String> newCategory =
						HashMultimap.create();
					newCategory.put(profileName, ccrData);
					mapByCategory.put(categoryName, newCategory);
				} else {
					Multimap<String, String> thisCategory =
						mapByCategory.get(categoryName);
					thisCategory.put(profileName, ccrData);
				}
				i++;
			}
			System.out.println(i + " CCR fragments for " + profileName);
		}
	}

	/*
	 * Return the clinical event category of a given profile entry.
	 */
	private String getCategory(ProfileEntry entry) {
		// extract the category element from XML and return its value
		Set<Category> catSet = entry.getCategories();
		String categoryName = null;
		for (Category cat: catSet) {
			if (cat.getTerm() != null && cat.getScheme() == null) {
				categoryName = cat.getTerm();
				break;
			}
		}
		return categoryName;
	}

	public static void main(String[] args) {
		System.out.println("Google Health Example");
		final String username = UsernamePassword.getUsernames().get(0);
		final String password = UsernamePassword.getPassword(username);

		/*
		 * Instantiate the object and populate the family profile.
		 */
		GoogleHealth gHealth = new GoogleHealth();
		if (gHealth.login(username, password)) {
			System.out.println("Logged in. Hooray!");
		} else {
			System.out.println("Login failed. Bummer.");
			return;
		}
		try {
			gHealth.createProfileLookupMap();
		} catch (Exception ex) {
			System.out.println("Could not find any profiles!");
		}
		System.out.println("Got profile lookup map.");
		for (String profileName: gHealth.profileLookup.keySet()) {
			System.out.println(profileName + " = " +
					gHealth.profileLookup.get(profileName));
		}
		try {
			gHealth.populateProfileToCCRMap();
		} catch (Exception e) {
			System.out.println("Could not get CCR for profiles!");
			e.printStackTrace();
		}
		System.out.println("Got individual CCR records");
		try {
			gHealth.populateCategoryToProfileMap();
		} catch (Exception e) {
			System.out.println("Could not put together a family profile!!!");
			e.printStackTrace();
		}
		System.out.println("Got family CCR records");

		/*
		 * Now display the profile data.
		 */

		/*
		 * Simple map for whole records by person.
		 * Key is profile name, value is a single CCRg document.
		 */
		System.out.println("------------BY FAMILY MEMBER --------");
		for (String profileId : gHealth.mapByProfile.keySet()) {
			System.out.println("profile name: " + profileId);
			// Apply XSLT here for display
			System.out.println("CCR is " +
					gHealth.mapByProfile.get(profileId).length() +
					" characters long.\n");
			//System.out.println(gHealth.mapByProfile.get(profileId));
		}

		/*
		 * Multiset for multiple people, multiple events for each. 
		 * key is profile name, value is a CCRg fragment.
		 */
		System.out.println("-------BY CATEGORY--------");
		for (String category : gHealth.mapByCategory.keySet()) {
			System.out.println("CATEGORY: " + category);
			Multimap<String, String> familyCcrByCategory =
				gHealth.mapByCategory.get(category);
			for (String profileName: familyCcrByCategory.keySet()) {
				System.out.println("Name: " + profileName);
				List<String> myEntries =
					Lists.newArrayList(familyCcrByCategory.get(profileName));
				int i = 0;
				for (String ccrFragment: myEntries) {
					// Apply XPath here to extract specific data elements
					System.out.println("CCR fragment " + ++i +
							" lenght is " + ccrFragment.length());
					//System.out.println(ccrFragment);
				}
				System.out.println();
			}
			System.out.println();
		}
	}
}
